/*
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Server
 *
 * main.c: Hauptprogramm des Servers
 */
#include "common/sockets.h"
#include "main.h"
#include "sync.h"
#include "login.h"
#include "user.h"
#include "catalog.h"
#include "score.h"
#include "clientthread.h"

int port; /**< Portnummer */

/**	\brief	Handler für Signale
 *  \param Signalnummer */

void INThandler(int sig){
     char  c;

     signal( sig, SIG_IGN );
     printf("Soll der Server wirklich beendet werden? [y]");
     c = getchar();
     if (c == 'y' || c == 'Y'){
    	  end_loader();
          exit(0);
     } else {
          signal(SIGINT, INThandler);
     }
}

/**	\brief Portnummer setzen
 *	\param port_str eingegebener Port als String*/
void set_port(char* port_str) {
	port = atoi(port_str);
}

/**	\brief Portnummer holen */
int get_port() {
	return port;
}


char* kat = { 0 }; /**< Katalogverzeichnis */


bool debug = false; /**< Debug */


pthread_t thread_client[MAX_USERS]; /**< Client-Threads */

/**	\brief Setze client_thread
 *	\param position Index des Thread
 *	\param thread Client-Thread*/
void set_client_thread(int position, pthread_t thread) {
	thread_client[position] = thread;
}

/**	\brief Hole client_thread-Array*/
pthread_t* get_client_threads() {
	return thread_client;
}


int file; /**< Single-Instance Überprüfung */

/**	\brief Setze Single-Instance */
void set_single_instance() {

	file = open("server_instance", O_WRONLY| O_CREAT, 0644);
	struct flock lock;

	if (file < 0) {
		debugPrint("Cannot create PID file\n");
		exit(1);
	}

	debugPrint("Acquiring file write lock\n");
	/* Important: lock before doing any IO */
	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	if (fcntl(file, F_SETLK, &lock) < 0) {
		infoPrint("Server läuft bereits..\n");
		exit(1);
	}

	if (ftruncate(file, 0) < 0) {
		infoPrint("Server läuft bereits..\n");
		exit(1);
	}

	int pid = getpid();
	if (write(file, &pid, sizeof(pid)) < sizeof(pid)) {
		debugPrint("write\n");
	}

	if (fsync(file) < 0) {
		debugPrint("fsync\n");
	}
}

/**
 * \brief Schließe Datei */
void close_file() {
	close(file);
}


/**	\brief Verarbeite Startparameter
 *	\param argc Anzahl der Startparameter
 *	\param argv Startparameter*/

void process_commands(int argc, char** argv) {
	// Momentan gewählte Option
	int opt;

	// Standard Port 8000
	char* port = { "8000" };

	// Schleife für abholen der Startparameter
	while (optind < argc) {

		// Hole nächstes Argument aus argv, berücksichtige Parameter p, c und d
		opt = getopt(argc, argv, "p:c:d");

		// Keine weiteren Argumente
		if (opt == -1) {
			break;
		}

		// Unterscheiden der Parameter
		switch (opt) {

		// Wenn kein Argument für die Option angegeben wurde
		case ':':
			infoPrint("Server beendet - fehlendes Argument...\n");
			infoPrint("server [-p PORT] -c KATALOGVERZECHNIS [-d (debug)]\n");
			infoPrint("Beispiel: ./server -p 8000 -c ./catalog\n");
			exit(0);
			break;

			// Option port
		case 'p':
			port = optarg;

			break;

			// Option catalog
		case 'c':
			kat = optarg;
			break;

			// Option debugMode
		case 'd':
			// Debug Modus an
			debugEnable();

			// Debug auf True, damit auch Loader mit Debug gestartet wird
			debug = true;

			break;

			// Option nicht bekannt
		case '?':
			/* getopt already printed an error message. */
			break;

		default:
			break;
		}
	}

	// Wenn kein Katalogverzeichnis angegeben wurde
	if (kat == 0) {
		infoPrint("Server beendet - fehlendes Argument...\n");
		infoPrint("server [-p PORT] -c KATALOGVERZECHNIS\n");
		infoPrint("Beispiel: ./server -p 8000 -c ./catalog\n");
		exit(0);
	}

	// Setze Portvariable
	set_port(port);
}


/**	\brief Start des Servers
 *	\param argc Anzahl der Startparameter
 *	\param argv Startparameter*/

int main(int argc, char ** argv) {

	set_single_instance();

	/* ** ** ** Programmstart - abholen der Argumente ** ** */

	setProgName(argv[0]);

	debugEnable();

	process_commands(argc, argv);

	infoPrint("Server Gruppe 09\n");

	/* ** ** ** Starte Loader und konfiguriere Pipes ** ** */

	// Loaderprozess mit Katalog starten
	if (start_loader(kat, debug) != 0) {
		infoPrint("Fehler im Loader.. Beende Server..\n");
		exit(0);
	}
	signal(SIGINT, INThandler);

	/* ** ** ** Initialisiere Sync-Elemente ** ** */

	sem_init(&exit_sem, 0, 0);
	sem_init(&score_agent_sem, 0, 0);
	sem_init(&quit_game_sem, 0, 0);
	if (create_status_mutex() == -1) {
		perror("Status Mutex: ");
	}

	/* ** ** ** Starte Login-Thread ** ** */

	// Starte Login-Thread
	pthread_t thread_login;
	if (pthread_create(&thread_login, NULL, login_thread, NULL) == -1) {
		perror("startLogin()");
		exit(0);
	}

	/* ** ** ** Starte Score-Agent ** ** */

	pthread_t thread_score;
	if (pthread_create(&thread_score, NULL, score_thread, NULL) == -1) {
		perror("startScoreAgent()");
		exit(0);
	}

	/* ** ** ** Programmende: Spielleiter verlässt Server ODER zuwenig Spieler ODER reguläres Spielende ** ** */

	// Blockiere bis Semaphor gesetzt
	sem_wait(&exit_sem);

	uint8_t client_id = get_client_id();

	int client_thread_return = 0;

	pthread_join(thread_client[client_id], (void*) &client_thread_return);

	switch (client_thread_return) {

	case SPIELLEITER_LEFT:
		infoPrint("Beende Server, weil Spielleiter Server verlassen hat\n");

		// Rufe Methode für vorzeitiges Server-Ende auf
		spielleiter_exit();

		// Beende Loader und SharedMem
		end_loader();

		//Schliesse Single-Instance File-Deskriptor
		close_file();

		exit(0);

	case ONLY_ONE:
		infoPrint("Beende Server, weil zuwenig Spieler\n");

		// Rufe Methode für vorzeitiges Spielende auf
		only_one();

		// Beende Loader und SharedMem
		end_loader();

		//Schliesse Single-Instance File-Deskriptor
		close_file();

		exit(0);

	case GAME_OVER:
		infoPrint("Alle Spieler fertig, beende Server\n");

		// Rufe Methode für Versenden der Ranks und abschliessen des Spiels auf
		game_over();

		// Beende Loader und SharedMem
		end_loader();

		//Schliesse Single-Instance File-Deskriptor
		close_file();

		exit(0);
	}

	/* ** ** ** Programmende: Warte auf Login-Thread ** ** */

	// Warte auf Ende von Login-Thread
	pthread_join(thread_login, NULL);

	//Schliesse Single-Instance File-Deskriptor
	close_file();

	return 0;
}

